    
    
    /**
    1、获取四个角点的算法并不固定，这里给到的是opencv自带的二维码解码和定位算法，但这套算法对于稍微小一点的二维码会完全失效，可以考虑别的定位算法工具
    2、定位时需要注意坐标系，opencv的坐标系y轴是向下的，opencv的图像色彩空间是BGR而不是RGB
    3、注意角点顺序矫正是必要的，我提供的矫正算法在左右偏移不到45度的情况下是完美矫正，如果还能优化到更大的角度也可以考虑
    4、使用透视仿射变换对图像进行矫正时，计算图像的宽高记得是勾股定理，不能简单的坐标加减
    5、提供两种清晰度检测算法，分别是拉普拉斯方差法和sobel算子高频能量法，这两者是可以结合的，阈值如何设定需要实践调整
    **/
    public Map<String, Object> slip_qr(Mat mat_src){
        String qrcode_content = null;
        Map<String,Object> result_map = new HashMap<>();
        File file_corrected = new File("qrcode_corrected_no");
        if (!file_corrected.exists()){
            file_corrected.mkdir();
        }
        File file_corner = new File("qrcode_corner_no");
        if (!file_corner.exists()){
            file_corner.mkdir();
        }
        String uuid = String.valueOf(cn.hutool.core.lang.UUID.randomUUID());
        boolean result = false;
        org.opencv.objdetect.QRCodeDetector qrCodeDetector = new org.opencv.objdetect.QRCodeDetector();
        // 检测并解码二维码
        Mat points = new Mat();
        // 使用OpenCV的QRCodeDetector进行二维码检测和解码
        // 参数说明：
        //   mat_src: 输入的源图像Mat对象
        //   points: 输出参数，存储检测到的二维码四个角点坐标
        // 返回值说明：
        //   decodedText: 解码后的二维码文本内容，如果为空表示解码失败
        String decodedText = qrCodeDetector.detectAndDecode(mat_src,points);
        // 备用解码方法，与detectAndDecode功能类似
        // 参数说明：
        //   mat_src: 输入的源图像Mat对象  
        //   points: 输入参数，需要先检测到的二维码角点坐标
        // 返回值说明：
        //   detect: 解码后的二维码文本内容
        String detect = qrCodeDetector.decode(mat_src,points);
        if (!decodedText.isEmpty()) {
            log.info("解码结果：" + decodedText);
            qrcode_content = decodedText;
            // 提取角点坐标
            Point[] srcPoints = extractPoints(points);

            if (srcPoints == null || srcPoints.length != 4) {
                log.info("无法提取四个角点。");
                points.release();
                result_map.put("justice",result);
                return result_map;
            }

            // 在原图上绘制角点
            Mat imageWithCorners = mat_src.clone();
            for (int i = 0; i < srcPoints.length; i++) {
                Imgproc.circle(imageWithCorners, srcPoints[i], 10, new Scalar(0, 0, 255), -1);
                Imgproc.putText(imageWithCorners, "P" + i, srcPoints[i], Imgproc.FONT_HERSHEY_SIMPLEX, 1, new Scalar(255, 0, 0), 2);
            }
            Imgcodecs.imwrite("image_with_corners.jpg", imageWithCorners);

            // 对角点进行排序，确保顺序为左上、右上、右下、左下
            srcPoints = sortCorners(srcPoints);

            double width = Math.sqrt(Math.pow(srcPoints[1].x - srcPoints[0].x, 2) + Math.pow(srcPoints[1].y - srcPoints[0].y, 2));
            double height = Math.sqrt(Math.pow(srcPoints[3].x - srcPoints[0].x, 2) + Math.pow(srcPoints[3].y - srcPoints[0].y, 2));
            double qrSize = (width + height) / 2;

            // 定义目标点
            Point[] dstPoints = new Point[] {
                    new Point(0, 0),                   // 左上角
                    new Point(qrSize - 1, 0),          // 右上角
                    new Point(qrSize - 1, qrSize - 1), // 右下角
                    new Point(0, qrSize - 1)           // 左下角
            };
=
            MatOfPoint2f srcMat = new MatOfPoint2f(srcPoints);
            MatOfPoint2f dstMat = new MatOfPoint2f(dstPoints);

            // 执行透视变换
            Mat perspectiveTransform = Imgproc.getPerspectiveTransform(srcMat, dstMat);

            // 透视校正后的二维码图像
            Mat qrCodeCorrected = new Mat();
            Imgproc.warpPerspective(mat_src, qrCodeCorrected, perspectiveTransform, new Size(qrSize, qrSize));

            // 保存校正后的二维码图像，到这里之后的就不用处理了，这里已经裁剪出需要的区域
            Imgcodecs.imwrite("qrcode_corrected/"+uuid+"_all.jpg", qrCodeCorrected);

            // 根据二维码版本计算模块数
            int version = getQRCodeVersion(decodedText);
            int numModules = 21 + (version - 1) * 4;  // 计算模块数
            double blockSize = qrSize / numModules;  // 计算每个模块的大小
            List<Mat> list_qr_corner = new ArrayList<>();
            // 裁剪并保存定位块
            list_qr_corner = cropAndSaveFinderPatterns(qrCodeCorrected, blockSize, qrSize,uuid);
            result = true;
            log.info("二维码校正与裁剪完成。");
            result_map.put("justice",result);
            result_map.put("list_qr_corner",list_qr_corner);
            result_map.put("qrcode_content",qrcode_content);
            points.release();
            imageWithCorners.release();
            srcMat.release();
            dstMat.release();
            perspectiveTransform.release();
            qrCodeCorrected.release();
            return result_map;
        } else {
            points.release();
            log.info("未检测到二维码。");
            result_map.put("justice",result);
            return result_map;
        }
    }

    private static Point[] sortCorners(Point[] corners) {
        // 计算中心点
        Point center = new Point(0, 0);
        for (Point corner : corners) {
            center.x += corner.x;
            center.y += corner.y;
        }
        center.x /= corners.length;
        center.y /= corners.length;

        // 根据角点相对于中心点的角度进行排序
        Arrays.sort(corners, new Comparator<Point>() {
            @Override
            public int compare(Point p1, Point p2) {
                double angle1 = (Math.toDegrees(Math.atan2(p1.y - center.y, p1.x - center.x)) + 360) % 360;
                double angle2 = (Math.toDegrees(Math.atan2(p2.y - center.y, p2.x - center.x)) + 360) % 360;
                return Double.compare(angle1, angle2);
            }
        });

        // 找到最靠近左上角的点作为起点，重新排列顺序
        int minIndex = 0;
        double minSum = Double.MAX_VALUE;
        for (int i = 0; i < corners.length; i++) {
            double sum = corners[i].x + corners[i].y;
            if (sum < minSum) {
                minSum = sum;
                minIndex = i;
            }
        }

        Point[] orderedCorners = new Point[4];
        for (int i = 0; i < 4; i++) {
            orderedCorners[i] = corners[(minIndex + i) % 4];
        }

        return orderedCorners;
    }
    //拉普拉斯方差 方差越大越清晰
    public static double varianceOfLaplacian(Mat src) {
        Mat gray = new Mat();

        if (src.channels() == 3) {
            Imgproc.cvtColor(src, gray, Imgproc.COLOR_BGR2GRAY);
        } else {
            gray = src.clone();
        }

        Mat laplacian = new Mat();
        Imgproc.Laplacian(gray, laplacian, CvType.CV_64F);

        MatOfDouble mu = new MatOfDouble();
        MatOfDouble sigma = new MatOfDouble();
        Core.meanStdDev(laplacian, mu, sigma);
        double variance = Math.pow(sigma.toArray()[0], 2);
        return variance;
    }

    //Sobel算子梯度能量法 能量越高越清晰
    public static double sobelFocusMeasure(Mat src) {
        Mat gray = new Mat();
        if (src.channels() == 3) {
            Imgproc.cvtColor(src, gray, Imgproc.COLOR_BGR2GRAY);
        } else {
            gray = src.clone();
        }

        Mat sobelX = new Mat();
        Mat sobelY = new Mat();
        Imgproc.Sobel(gray, sobelX, CvType.CV_64F, 1, 0);
        Imgproc.Sobel(gray, sobelY, CvType.CV_64F, 0, 1);
        
        Mat gradientMagnitude = new Mat();
        Core.magnitude(sobelX, sobelY, gradientMagnitude);
        
        // 计算均值和标准差
        MatOfDouble mu = new MatOfDouble();
        MatOfDouble sigma = new MatOfDouble();
        Core.meanStdDev(gradientMagnitude, mu, sigma);
        double energy = Math.pow(sigma.toArray()[0], 2);
        return energy;
    }